 /*******************************************************************************
  * Copyright (c) 2005, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.ui.internal.navigator.actions;

 import java.util.ArrayList ;
 import java.util.Collection ;
 import java.util.HashSet ;
 import java.util.Iterator ;
 import java.util.LinkedHashMap ;
 import java.util.LinkedHashSet ;
 import java.util.Map ;
 import java.util.Set ;

 import org.eclipse.core.runtime.Assert;
 import org.eclipse.core.runtime.IConfigurationElement;
 import org.eclipse.core.runtime.ISafeRunnable;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.SafeRunner;
 import org.eclipse.jface.viewers.IStructuredSelection;
 import org.eclipse.jface.viewers.StructuredSelection;
 import org.eclipse.ui.actions.ActionContext;
 import org.eclipse.ui.internal.navigator.NavigatorPlugin;
 import org.eclipse.ui.internal.navigator.extensions.NavigatorContentRegistryReader;
 import org.eclipse.ui.navigator.INavigatorContentService;
 import org.eclipse.ui.navigator.Priority;

 /**
  * Manages descriptors consumed from the 'actionProvider' elements of the
  * <b>org.eclipse.ui.navigator.navigatorContent</b> extension point.
  *
  * @since 3.2
  *
  */
 public class CommonActionDescriptorManager {

     private static final CommonActionProviderDescriptor[] NO_DESCRIPTORS = new CommonActionProviderDescriptor[0];

     private static final CommonActionDescriptorManager INSTANCE = new CommonActionDescriptorManager();

     private CommonActionDescriptorManager() {

         new ActionProviderRegistry().readRegistry();
     }

     /**
      * @return the singleton instance of the registry
      */
     public static CommonActionDescriptorManager getInstance() {
         return INSTANCE;
     }

     /* Provides a map of (ids, CommonActionProviderDescriptor)-pairs. */
     private final Map dependentDescriptors = new LinkedHashMap ();

     /* Provides a map of (ids, CommonActionProviderDescriptor)-pairs. */
     private final Set overridingDescriptors = new LinkedHashSet ();

     /* Provides a map of (ids, CommonActionProviderDescriptor)-pairs. */
     private final Map rootDescriptors = new LinkedHashMap ();

     /**
      *
      * @param aDescriptor
      * A valid descriptor to begin managing.
      */
     protected void addActionDescriptor(
             CommonActionProviderDescriptor aDescriptor) {

         if (aDescriptor.getDependsOnId() == null) {
             rootDescriptors.put(aDescriptor.getDefinedId(), aDescriptor);
         } else {
             dependentDescriptors.put(aDescriptor.getDefinedId(), aDescriptor);
         }

         if (aDescriptor.getOverridesId() != null) {
             overridingDescriptors.add(aDescriptor);
         }
     }

     /**
      * Orders the set of availabe descriptors based on the order defined by the
      * <i>dependsOn</i> attribute from the <actionProvider /> element in
      * <b>org.eclipse.ui.navigator.navigatorContent</b>
      *
      */
     protected void computeOrdering() {
         CommonActionProviderDescriptor dependentDescriptor;
         CommonActionProviderDescriptor requiredDescriptor;

         CommonActionProviderDescriptor descriptor;
         CommonActionProviderDescriptor overriddenDescriptor;
         for (Iterator iter = overridingDescriptors.iterator(); iter.hasNext();) {
             descriptor = (CommonActionProviderDescriptor) iter.next();
             if (rootDescriptors.containsKey(descriptor.getOverridesId())) {
                 overriddenDescriptor = (CommonActionProviderDescriptor) rootDescriptors
                         .get(descriptor.getOverridesId());
                 overriddenDescriptor.addOverridingDescriptor(descriptor);
             } else if (dependentDescriptors.containsKey(descriptor
                     .getOverridesId())) {
                 overriddenDescriptor = (CommonActionProviderDescriptor) dependentDescriptors
                         .get(descriptor.getOverridesId());
                 overriddenDescriptor.addOverridingDescriptor(descriptor);
             }

         }

         Collection unresolvedDependentDescriptors = new ArrayList (
                 dependentDescriptors.values());

         for (Iterator iter = dependentDescriptors.values().iterator(); iter
                 .hasNext();) {
             dependentDescriptor = (CommonActionProviderDescriptor) iter.next();
             requiredDescriptor = (CommonActionProviderDescriptor) rootDescriptors
                     .get(dependentDescriptor.getDependsOnId());
             if (requiredDescriptor == null) {
                 requiredDescriptor = (CommonActionProviderDescriptor) dependentDescriptors
                         .get(dependentDescriptor.getDependsOnId());
             }
             if (requiredDescriptor != null) {
                 requiredDescriptor.addDependentDescriptor(dependentDescriptor);
                 unresolvedDependentDescriptors.remove(dependentDescriptor);
             }

         }

         dependentDescriptors.clear();

         if (!unresolvedDependentDescriptors.isEmpty()) {
             StringBuffer errorMessage = new StringBuffer (
                     "There were unresolved dependencies for action provider extensions to a Common Navigator.\n" + //$NON-NLS-1$
 "Verify that the \"dependsOn\" attribute for each <actionProvider /> element is valid."); //$NON-NLS-1$

             CommonActionProviderDescriptor[] unresolvedDescriptors = (CommonActionProviderDescriptor[]) unresolvedDependentDescriptors
                     .toArray(new CommonActionProviderDescriptor[unresolvedDependentDescriptors
                             .size()]);
             for (int i = 0; i < unresolvedDescriptors.length; i++) {
                 errorMessage
                         .append(
                                 "\nUnresolved dependency specified for actionProvider: ").append(unresolvedDescriptors[i].getDefinedId()); //$NON-NLS-1$
 }

             NavigatorPlugin.log(IStatus.WARNING, 0, errorMessage.toString(),
                     null);

         }
         unresolvedDependentDescriptors.clear();

     }

     /**
      *
      * @param aContentService
      * The content service to use when filtering action providers;
      * only action providers bound directly or indirectly will be
      * returned.
      * @param aContext
      * The action context that contains a valid selection.
      * @return An array of visible, active, and enabled CommonActionProviders.
      * See <b>org.eclipse.ui.navigator.navigatorContent</b> for the
      * details of what each of these adjectives implies.
      */
     public CommonActionProviderDescriptor[] findRelevantActionDescriptors(
             INavigatorContentService aContentService, ActionContext aContext) {
         Assert.isNotNull(aContext);
         IStructuredSelection structuredSelection = null;
         if (aContext.getSelection() instanceof IStructuredSelection) {
             structuredSelection = (IStructuredSelection) aContext
                     .getSelection();
         } else {
             structuredSelection = StructuredSelection.EMPTY;
         }

         Set blockedProviders = new HashSet ();
         CommonActionProviderDescriptor actionDescriptor = null;
         Set providers = new LinkedHashSet ();
         for (Iterator providerItr = rootDescriptors.values().iterator(); providerItr
                 .hasNext();) {
             actionDescriptor = (CommonActionProviderDescriptor) providerItr
                     .next();
             addProviderIfRelevant(aContentService, structuredSelection,
                     actionDescriptor, providers, blockedProviders);
         }
         if (providers.size() > 0) {
             providers.removeAll(blockedProviders);
             return (CommonActionProviderDescriptor[]) providers
                     .toArray(new CommonActionProviderDescriptor[providers
                             .size()]);
         }
         return NO_DESCRIPTORS;
     }

     /**
      * @param aContentService
      * @param structuredSelection
      * @param actionDescriptor
      * @param providers
      */
     private boolean addProviderIfRelevant(
             INavigatorContentService aContentService,
             IStructuredSelection structuredSelection,
             CommonActionProviderDescriptor actionDescriptor, Set providers, Set blockedProviders) {
         if (isVisible(aContentService, actionDescriptor)
                 && actionDescriptor.isEnabledFor(structuredSelection)) {
             
             if(actionDescriptor.hasOverridingDescriptors()) {
                 for (Iterator iter = actionDescriptor.overridingDescriptors(); iter.hasNext();) {
                     CommonActionProviderDescriptor descriptor = (CommonActionProviderDescriptor) iter.next();
                     if(addProviderIfRelevant(aContentService, structuredSelection, descriptor, providers, blockedProviders)) {
                         while(iter.hasNext())
                             blockedProviders.add(iter.next());
                         return true;
                     }
                     
                 }
             }
             providers.add(actionDescriptor);
             if (actionDescriptor.hasDependentDescriptors()) {
                 for (Iterator iter = actionDescriptor.dependentDescriptors(); iter
                         .hasNext();) {
                     addProviderIfRelevant(aContentService, structuredSelection,
                             (CommonActionProviderDescriptor) iter.next(),
                             providers, blockedProviders);
                 }
             }
             return true;
         }
         return false;
     }

     private boolean isVisible(INavigatorContentService aContentService,
             CommonActionProviderDescriptor descriptor) {
         if (descriptor.isNested()) {
             return aContentService.isActive(descriptor.getId())
                     && aContentService.isVisible(descriptor.getId());
         }
         return aContentService.getViewerDescriptor().isVisibleActionExtension(
                 descriptor.getId());
     }

     private class ActionProviderRegistry extends NavigatorContentRegistryReader {

         public void readRegistry() {
             super.readRegistry();
             computeOrdering();
         }

         protected boolean readElement(IConfigurationElement anElement) {
             if (TAG_ACTION_PROVIDER.equals(anElement.getName())) {
                 addActionDescriptor(new CommonActionProviderDescriptor(
                         anElement));
                 return true;
             } else if (TAG_NAVIGATOR_CONTENT.equals(anElement.getName())) {
                 
                 IConfigurationElement[] actionProviders = anElement.getChildren(TAG_ACTION_PROVIDER);
                 
                 if (actionProviders.length > 0) {
                     
                     IConfigurationElement defaultEnablement = null;
                     IConfigurationElement[] inheritedEnablement = anElement.getChildren(TAG_ENABLEMENT);
                     if (inheritedEnablement.length == 0) {
                         inheritedEnablement = anElement.getChildren(TAG_POSSIBLE_CHILDREN);
                     }
                     
                     defaultEnablement = inheritedEnablement.length == 1 ? inheritedEnablement[0] : null;
   
                     Priority defaultPriority = Priority.get(anElement.getAttribute(ATT_PRIORITY));
                     
                     
                     if(defaultEnablement == null) {
                         NavigatorPlugin.logError(0,
                             "An actionProvider has been defined as the child " + //$NON-NLS-1$
 "of a navigatorContent extension that does not specify " + //$NON-NLS-1$
 "an <enablement/> or <possibleChildren /> expression. Please " + //$NON-NLS-1$
 "review the documenation and correct this error.", null); //$NON-NLS-1$
 }
                     for (int i = 0; i < actionProviders.length; i++) {
                         if(defaultEnablement == null) {
                             NavigatorPlugin.logError(0,
                                             "Disabling actionProvider: " + actionProviders[i].getAttribute(ATT_ID), null); //$NON-NLS-1$
 } else {
                             SafeRunner.run(new AddProviderSafeRunner(actionProviders[i], defaultEnablement, defaultPriority, anElement));
                         }
                     }
                 }
                 return true;
             }
             return super.readElement(anElement);
         }
     
         private class AddProviderSafeRunner implements ISafeRunnable {
             
             private IConfigurationElement parentElement;
             private IConfigurationElement defaultEnablement;
             private IConfigurationElement actionProvider;
             private Priority defaultPriority;

             protected AddProviderSafeRunner(IConfigurationElement actionProvider,
                                              IConfigurationElement defaultEnablement,
                                              Priority defaultPriority,
                                              IConfigurationElement parentElement) {
                 this.actionProvider = actionProvider;
                 this.defaultEnablement = defaultEnablement;
                 this.defaultPriority = defaultPriority;
                 this.parentElement = parentElement;
             }
             
             /* (non-Javadoc)
              * @see org.eclipse.core.runtime.ISafeRunnable#run()
              */
             public void run() throws Exception {
                 addActionDescriptor(new CommonActionProviderDescriptor(
                             actionProvider, defaultEnablement, defaultPriority, parentElement
                                     .getAttribute(ATT_ID), true));
             }
             
             /* (non-Javadoc)
              * @see org.eclipse.core.runtime.ISafeRunnable#handleException(java.lang.Throwable)
              */
             public void handleException(Throwable t) {
                 NavigatorPlugin.logError(0, "Recovering from error while parsing actionProviders.", t); //$NON-NLS-1$
 }
             
             
         }
     }


 }

